﻿using System;
using System.Collections.Generic;
using System.Linq;
using StockholmsStad.Data.Context;
using StockholmsStad.Data.ContractFactory;
using StockholmsStad.Data.Entity;
using StockholmsStad.Data.Interfaces;
using StockholmsStad.Utilities.Enums;
using StockholmsStad.Utilities.Exceptions;
using Ceremony = StockholmsStad.Runtime.Serialization.DataContract.Ceremony;
using Couple = StockholmsStad.Runtime.Serialization.DataContract.Couple;
using History=StockholmsStad.Runtime.Serialization.DataContract.History;
using Ticket = StockholmsStad.Runtime.Serialization.DataContract.Ticket;
using TicketInformation = StockholmsStad.Runtime.Serialization.DataContract.TicketInformation;
using User = StockholmsStad.Runtime.Serialization.DataContract.User;
using StockholmsStad.Runtime.Serialization.DataContract;
using StockholmsStad.Runtime.Serialization.DataContract.Enums;

namespace StockholmsStad.Data.DatabaseAgents
{
    internal class TicketAgent : DBAgentBase, ITicketAgent
    {
        #region ITicketAgent Members

        public int CreateTicket(IDataContext context, Couple couple, Ceremony ceremony)
        {
            int ticketId;

            ICoupleAgent coupleAgent = AgentFactory.GetCoupleAgent();
            ICeremonyAgent ceremonyAgent = AgentFactory.GetCeremonyAgent();

            try
            {
                int coupleId = coupleAgent.AddCouple(context, couple);
                int ceremonyId = ceremonyAgent.AddCeremony(context, ceremony);

                var ticket = new Entity.Ticket { CeremonyId = ceremonyId, CoupleId = coupleId };

                ticketId = CreateTicket(context, ticket);

                if (couple.Person1.ProtectedIdentity || couple.Person2.ProtectedIdentity)
                {
                    ceremonyAgent.UpdateCeremonyWithProtectedIdentity(context, ticket.CeremonyId);
                }
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }

            return ticketId;
        }

        public int CreateTicket(IDataContext context, Couple couple, Ceremony ceremony, int userId)
        {
            int ticketId;

            ICoupleAgent coupleAgent = AgentFactory.GetCoupleAgent();
            ICeremonyAgent ceremonyAgent = AgentFactory.GetCeremonyAgent();

            try
            {
                int coupleId = coupleAgent.AddCouple(context, couple);
                int ceremonyId = ceremonyAgent.AddCeremony(context, ceremony);

                var ticket = new Entity.Ticket
                                 {
                                     CeremonyId = ceremonyId,
                                     CoupleId = coupleId,
                                     UserId = userId,
                                     TicketStatusId = (int) TicketStatusType.Started
                                 };

                ticketId = CreateTicket(context, ticket);

                if (couple.Person1.ProtectedIdentity || couple.Person2.ProtectedIdentity)
                {
                    ceremonyAgent.UpdateCeremonyWithProtectedIdentity(context, ticket.CeremonyId);
                }
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }

            return ticketId;
        }

        public int GetTicketId(IDataContext context, string username)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                var userAgent = AgentFactory.GetUserAgent();
                var user = userAgent.GetUser(context, username);

                if(user == null)
                {
                    throw new StockholmStadSystemException(SystemErrorCode.UserNotExists);
                }

                var ticket = dbCtx.Tickets.Where(t => t.UserId == user.UserId).FirstOrDefault();

                if (ticket == null)
                {
                    throw new StockholmStadSystemException(SystemErrorCode.UserHasNoTicket);
                }

                return ticket.TicketId;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }  
        }

        public Couple GetCouple(IDataContext context, int ticketId)
        {
            var ticketAgent = AgentFactory.GetTicketAgent();
            var coupleAgent = AgentFactory.GetCoupleAgent();

            try
            {
                var ticket = ticketAgent.GetTicket(context, ticketId);

                if(ticket == null)
                    throw new StockholmStadSystemException(SystemErrorCode.TicketNotExists);

                return coupleAgent.GetCouple(context, ticket.Couple.CoupleId.Value);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }
        }

        public Ticket GetTicket(IDataContext context, int ticketId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var ticket = dbCtx.Tickets.Where(t => t.TicketId == ticketId).SingleOrDefault();
                if (ticket == null)
                    return null;

                int ceremonyId = ticket.CeremonyId;

                ICeremonyAgent agent = AgentFactory.GetCeremonyAgent();

                var contractCeremony = agent.GetCeremony(context, ceremonyId);

                return FactoryAgent.GetTicketFactory().CreateContractObject(ticket, contractCeremony);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }
        }

        public int? GetBookingId(IDataContext context, int ticketId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var booking = dbCtx.Bookings.Where(b => b.TicketId == ticketId).FirstOrDefault();

                if(booking == null)
                    throw new StockholmStadSystemException(SystemErrorCode.BookingNotExist);

                return booking.BookingId;
                //var ticket = SelectByPrimaryKey<Entity.Ticket>(ticketId, dbCtx);

                //if (ticket != null && ticket.Booking != null)
                //    return ticket.Booking.BookingId;

                //return null;
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }

        public List<CouplesSummaryItem> GetTickets(IDataContext context, string partOfName, string partOfBirthday, TicketStatusType ticketStatus)
        {
            try
            {
                var factory = FactoryAgent.GetTicketFactory();
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                bool isActive = ticketStatus == TicketStatusType.CarriedOut || ticketStatus == TicketStatusType.NotCarriedOut
                    ? false : true;

                var tickets = dbCtx.FindTickets(isActive, partOfBirthday, partOfName);
                return tickets.Select(t => factory.CreateCouplesObject(t)).ToList();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public void UpdateCouple(IDataContext context, int ticketId, Couple couple)
        {
            ICoupleAgent coupleAgent = AgentFactory.GetCoupleAgent();
            ICeremonyAgent ceremonyAgent = AgentFactory.GetCeremonyAgent();
            try
            {
                StockholmsStadDataContext dbCtx = ((DataContext)context).StockholmsStadDataContext;
                var ticket = dbCtx.Tickets.Where(t => t.TicketId == ticketId).FirstOrDefault();
                coupleAgent.UpdateCouple(context, ticket.CoupleId, couple);
                if (couple.Person1.ProtectedIdentity || couple.Person2.ProtectedIdentity)
                {
                    ceremonyAgent.UpdateCeremonyWithProtectedIdentity(context, ticket.CeremonyId);
                }
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }            
        }

        public void UpdateTicketStatus(IDataContext context, int ticketId, TicketStatusType ticketStatusType)
        {
            try
            {
                StockholmsStadDataContext dbCtx = ((DataContext)context).StockholmsStadDataContext;

                var ticket = dbCtx.Tickets.Where(t => t.TicketId == ticketId).FirstOrDefault();

                if(ticket == null)
                    throw new StockholmStadSystemException(SystemErrorCode.TicketNotExists);

                // set association property rather than foreign key
                var status = dbCtx.TicketStatus.Where(t => t.TicketStatusId == (int)ticketStatusType).FirstOrDefault();
                ticket.TicketStatus = status;
                dbCtx.SubmitChanges();
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }
        }


        public void UpdateTicket(IDataContext context, int ticketId, Couple couple, Ceremony ceremony, string username)
        {
            ICoupleAgent coupleAgent = AgentFactory.GetCoupleAgent();
            ICeremonyAgent ceremonyAgent = AgentFactory.GetCeremonyAgent();
            IUserAgent userAgent = AgentFactory.GetUserAgent();

            try
            {
                StockholmsStadDataContext dbCtx = ((DataContext)context).StockholmsStadDataContext;
                Entity.Ticket ticket = (from t in dbCtx.Tickets
                                        where t.TicketId == ticketId
                                        select t).First();
             
                coupleAgent.UpdateCouple(context, ticket.CoupleId, couple);
                ceremonyAgent.UpdateCeremony(context, ticket.CeremonyId, ceremony);
                if (couple.Person1.ProtectedIdentity || couple.Person2.ProtectedIdentity)
                {
                    ceremonyAgent.UpdateCeremonyWithProtectedIdentity(context, ticket.CeremonyId);
                }
                userAgent.UpdateUser(context, ticket.CoupleId, username);
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }

        public Ticket GetTicket(IDataContext context, User user)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                Entity.Ticket ticket = dbCtx.Tickets.Where(t => t.UserId == user.UserId).FirstOrDefault();
                if (ticket == null)
                    return null;
                return GetTicket(context, ticket.TicketId);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }
        }

        //public void UpdateTicketBookingId(IDataContext context, int ticketId, int? bookingId)
        //{
        //    try
        //    {
        //        StockholmsStadDataContext dbCtx = context.GetInternalContext();

        //        var ticket = SelectByPrimaryKey<Entity.Ticket>(ticketId, dbCtx);

        //        if (ticket != null)
        //        {
        //            // set association property rather than foreign key
        //            Entity.Booking booking;

        //            if(bookingId.HasValue)
        //                booking = dbCtx.Bookings.Single(b => b.BookingId == bookingId.Value);
        //            else
        //                booking = null;

        //            ticket.Booking = booking;
        //            dbCtx.SubmitChanges();
        //        }
        //    }
        //    catch (Exception ex)
        //    {
        //        throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
        //    }
        //}

        public void InsertNotice(IDataContext context, int ticketId, string message, string user)
        {
            try
            {
                const bool isNotice = true;
                const bool isPublic = false;
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                dbCtx.InsertHistory(user, message, ticketId, isPublic, isNotice);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public void InsertHistory(IDataContext context, int? ticketId, string message, string user, bool isPublic)
        {
            try
            {
                const bool isNotice = false;
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                dbCtx.InsertHistory(user, message, ticketId, isPublic, isNotice);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public List<History> GetHistoryItems(IDataContext context, int ticketId, bool isPublic)
        {
            try
            {
                var factory = new TicketFactory();
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                // Hämta inga notiser, endast historik
                const bool isNotice = false;

                if (isPublic)
                {
                    return dbCtx.Histories.OrderBy(h => h.ModifiedDate).
                        Where(h => h.IsPublic == isPublic && h.IsNotice == isNotice && h.TicketId == ticketId).
                        Select(h => factory.CreateContractObject(h)).ToList();
                }

                return dbCtx.Histories.OrderBy(h => h.ModifiedDate).
                    Where(h => h.IsNotice == isNotice && h.TicketId == ticketId).
                    Select(h => factory.CreateContractObject(h)).ToList();
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }
        }

        public List<History> GetNotices(IDataContext context)
        {
            try
            {
                var factory = new TicketFactory();
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                const bool isNotice = true;

                var notices = dbCtx.Histories.OrderBy(h => h.ModifiedDate).Where(h => h.IsNotice == isNotice)
                        .Select(h => factory.CreateContractObject(h)).ToList();
                
                return notices;
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }            
        }

        public void InactivateNotice(IDataContext context, int historyId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var history = dbCtx.Histories.Where(h => h.HistoryId == historyId).FirstOrDefault();

                if(history == null)
                    throw new StockholmStadSystemException(SystemErrorCode.HistoryNotExists);

                history.IsNotice = false;
                dbCtx.SubmitChanges();
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }            
        }
        
        public Ceremony GetCeremony(IDataContext context, int ticketId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var ticket = dbCtx.Tickets.Where(t => t.TicketId == ticketId).SingleOrDefault();

                if (ticket == null)
                    throw new StockholmStadSystemException(SystemErrorCode.TicketNotExists);

                var ceremonyAgent = AgentFactory.GetCeremonyAgent();
                var ceremony = ceremonyAgent.GetCeremony(context, ticket.CeremonyId);

                var bookingAgent = AgentFactory.GetBookingAgent();

                if(bookingAgent.HasTicketBooking(context, ticketId))
                {
                    var booking = bookingAgent.GetBooking(context, ticketId);
                    ceremonyAgent.GetImpedimentDates(context, booking, ceremony);
                }

                return ceremony;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }
        }

        public TicketInformation GetTicketInformation(IDataContext context, int ticketId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var ticketFactory = new TicketFactory();
                var info = dbCtx.TicketInformations.Where(t => t.TicketId == ticketId).FirstOrDefault();

                return ticketFactory.CreateContractObject(info);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }
        }

        public int AddTicketInformation(IDataContext context, TicketInformation ticketInformation)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var ticketFactory = new TicketFactory();
                var entityTicket = ticketFactory.CreateEntityObject(ticketInformation);
                return Insert(entityTicket, dbCtx);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }            
        } 

        public void UpdateTicketInformation(IDataContext context, TicketInformation ticketInformation)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var entityTicket = dbCtx.TicketInformations.Where(t => t.TicketId == ticketInformation.TicketId).FirstOrDefault();

                if (entityTicket != null)
                {
                    entityTicket.InformationText = ticketInformation.InformationText;
                    dbCtx.SubmitChanges();
                }
                else
                {
                    entityTicket = new Entity.TicketInformation
                                       {
                                           InformationText = ticketInformation.InformationText,
                                           TicketId = ticketInformation.TicketId
                                       };
                    Insert(entityTicket, dbCtx);
                }
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }            
        }

        /// <summary>
        /// Hämtar icke-lediga tidsblock som en lista med start-slut-datum (för kalender som 
        /// endast tar "utblockade" datum. 
        /// </summary>
        /// <param name="context"></param>
        /// <returns>En lista med </returns>
        public List<DateRange> GetNonAvailableDates(IDataContext context)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var dates = dbCtx.GetNonAvailableDates().ToList();

                var list = new List<DateRange>();

                foreach(var line in dates)
                {
                    list.Add(new DateRange
                                 {
                                     StartDate = line.StartDate.Value,
                                     EndDate = line.EndDate.Value,
                                 });
                }

                return list;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }                        
        }
    
        /// <summary>
        /// Hämtar icke fullbokade tidsblock som en lista med start-slut-datum (för kalender som 
        /// endast tar "utblockade" datum. 
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public List<DateRange> GetNonFullyBookedDates(IDataContext context)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var dates = dbCtx.GetNonFullyBookedDates().ToList();

                var list = new List<DateRange>();

                foreach (var line in dates)
                {
                    list.Add(new DateRange
                    {
                        StartDate = line.StartDate.Value,
                        EndDate = line.EndDate.Value,
                    });
                }

                return list;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }            
        }

        /// <summary>
        /// Skicka mail till användaren vid nyregistrering
        /// </summary>
        ///<param name="context">Det databascontext som operationen kör i</param>
        ///<param name="userName">Användarnamn</param>
        ///<param name="password"></param>
        ///<param name="fullName">Namnet på den person som ska ha uppgifterna. </param>
        public void SendRegistrationMail(IDataContext context, string userName, string password, string fullName)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                dbCtx.SendRegistrationMail(userName, fullName, userName, password);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }
        
        /// <summary>
        /// Skicka mail till användaren vid nyregistrering
        /// </summary>
        ///<param name="context">Det databascontext som operationen kör i</param>
        ///<param name="person">Vilken person som ska ha uppgifterna</param>
        ///<param name="userName">Användarnamn</param>
        ///<param name="password"></param>
        public void SendRegistrationMail(IDataContext context, PersonDetails person, string userName, string password)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                dbCtx.SendRegistrationMail(person.Email, person.FullName, userName, password);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        /// <summary>
        /// Skicka mail till användaren vid nyregistrering
        /// </summary>
        ///<param name="context">Det databascontext som operationen kör i</param>
        ///<param name="user">Användarnamn</param>
        ///<param name="password"></param>
        public void SendRegistrationMail(IDataContext context, Runtime.Serialization.DataContract.ParvisUser user, string password)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                dbCtx.SendRegistrationMail(user.Username, string.Format("{0} {1}", user.FirstName, user.LastName),
                                           user.Username, password);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        ///<param name="context">Det databascontext som operationen kör i</param>
        ///<param name="user">Användarnamn</param>
        public void SendRegistrationInParvisMail(IDataContext context, Runtime.Serialization.DataContract.ParvisUser user)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                dbCtx.SendRegistrationInParvisMail(user.Username, user.Username, user.Password);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        #endregion

        private static int CreateTicket(IDataContext context, Entity.Ticket ticket)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                ticket.TicketStatusId = (int) TicketStatusType.Started;
                dbCtx.Tickets.InsertOnSubmit(ticket);
                dbCtx.SubmitChanges();
                return ticket.TicketId;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }


    }
}